/*****************************************************************************
 *                                                                           *
 *  This file is part of the BeanShell Java Scripting distribution.          *
 *  Documentation and updates may be found at http://www.beanshell.org/      *
 *                                                                           *
 *  Sun Public License Notice:                                               *
 *                                                                           *
 *  The contents of this file are subject to the Sun Public License Version  *
 *  1.0 (the "License"); you may not use this file except in compliance with *
 *  the License. A copy of the License is available at http://www.sun.com    * 
 *                                                                           *
 *  The Original Code is BeanShell. The Initial Developer of the Original    *
 *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
 *  (C) 2000.  All Rights Reserved.                                          *
 *                                                                           *
 *  GNU Public License Notice:                                               *
 *                                                                           *
 *  Alternatively, the contents of this file may be used under the terms of  *
 *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
 *  provisions of LGPL are applicable instead of those above. If you wish to *
 *  allow use of your version of this file only under the  terms of the LGPL *
 *  and not to allow others to use your version of this file under the SPL,  *
 *  indicate your decision by deleting the provisions above and replace      *
 *  them with the notice and other provisions required by the LGPL.  If you  *
 *  do not delete the provisions above, a recipient may use your version of  *
 *  this file under either the SPL or the LGPL.                              *
 *                                                                           *
 *  Patrick Niemeyer (pat@pat.net)                                           *
 *  Author of Learning Java, O'Reilly & Associates                           *
 *  http://www.pat.net/~pat/                                                 *
 *                                                                           *
 *****************************************************************************/
package bsh;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Vector;

import cloudspace.vm.VM;
import cloudspace.vm.javassist.JavassistBootloader;

/**
 * All of the reflection API code lies here. It is in the form of static
 * utilities. Maybe this belongs in LHS.java or a generic object wrapper class.
 */
/*
 * Note: This class is messy. The method and field resolution need to be
 * rewritten. Various methods in here catch NoSuchMethod or NoSuchField
 * exceptions during their searches. These should be rewritten to avoid having
 * to catch the exceptions. Method lookups are now cached at a high level so
 * they are less important, however the logic is messy.
 */
class Reflect
{
	/**
	 * Invoke method on arbitrary object instance. invocation may be static
	 * (through the object instance) or dynamic. Object may be a bsh scripted
	 * object (bsh.This type).
	 * 
	 * @return the result of the method call
	 */
	public static Object invokeObjectMethod(Object object, String methodName, Object[] args,
			Interpreter interpreter, CallStack callstack, SimpleNode callerInfo)
			throws ReflectError, EvalError, InvocationTargetException
	{
		// Bsh scripted object
		if (object instanceof This && !This.isExposedThisMethod(methodName))
			return ((This) object).invokeMethod(methodName, args, interpreter, callstack,
					callerInfo, false/* delcaredOnly */
			);

		// Plain Java object, find the java method
		try
		{
			BshClassManager bcm = interpreter == null ? null : interpreter.getClassManager();
			Class clas = object.getClass();
			String proxyName = JavassistBootloader.getProxy(clas.getName());
			if (proxyName != null)
			{
				try
				{
					Class<?> proxyClass = VM.currentVM().getLoader().loadClass(proxyName);
					clas = proxyClass;
					Object[] newArgs = new Object[args.length + 1];
					methodName = "_" + methodName;
					newArgs[0] = object;
					int index = 1;
					for (Object arg : args)
					{
						// Add args
						newArgs[index] = arg;
						index++;
					}
					args = newArgs;
				}
				catch (Exception e)
				{
					;
				}
			}
			Method method = resolveExpectedJavaMethod(bcm, clas, object, methodName, args, false);

			return invokeMethod(method, object, args);
		}
		catch (UtilEvalError e)
		{
			throw e.toEvalError(callerInfo, callstack);
		}
	}

	/**
	 * Invoke a method known to be static. No object instance is needed and
	 * there is no possibility of the method being a bsh scripted method.
	 */
	public static Object invokeStaticMethod(BshClassManager bcm, Class clas, String methodName,
			Object[] args) throws ReflectError, UtilEvalError, InvocationTargetException
	{
		Interpreter.debug("invoke static Method");
		try
		{
			String proxyName = JavassistBootloader.getProxy(clas.getName());
			if (proxyName != null)
			{
				Class<?> proxyClass = VM.currentVM().getLoader().loadClass(proxyName);
				clas = proxyClass;
				Method[] meths = proxyClass.getMethods();
				methodName = "_" + methodName;
			}
		}
		catch (Exception e)
		{

		}
		Method method = resolveExpectedJavaMethod(bcm, clas, null, methodName, args, true);
		return invokeMethod(method, null, args);
	}

	/**
	 * Invoke the Java method on the specified object, performing needed type
	 * mappings on arguments and return values.
	 * 
	 * @param args
	 *            may be null
	 */
	static Object invokeMethod(Method method, Object object, Object[] args) throws ReflectError,
			InvocationTargetException
	{
		if (args == null)
			args = new Object[0];

		logInvokeMethod("Invoking method (entry): ", method, args);

		// Map types to assignable forms, need to keep this fast...
		Object[] tmpArgs = new Object[args.length];
		Class[] types = method.getParameterTypes();
		try
		{
			for (int i = 0; i < args.length; i++)
				tmpArgs[i] = Types.castObject(args[i]/* rhs */, types[i]/* lhsType */,
						Types.ASSIGNMENT);
		}
		catch (UtilEvalError e)
		{
			throw new InterpreterError("illegal argument type in method invocation: " + e);
		}

		// unwrap any primitives
		tmpArgs = Primitive.unwrap(tmpArgs);

		logInvokeMethod("Invoking method (after massaging values): ", method, tmpArgs);

		try
		{
			Object returnValue = method.invoke(object, tmpArgs);
			if (returnValue == null)
				returnValue = Primitive.NULL;
			Class returnType = method.getReturnType();

			return Primitive.wrap(returnValue, returnType);
		}
		catch (IllegalAccessException e)
		{
			throw new ReflectError("Cannot access method "
					+ StringUtil.methodString(method.getName(), method.getParameterTypes())
					+ " in '" + method.getDeclaringClass() + "' :" + e);
		}
	}

	public static Object getIndex(Object array, int index) throws ReflectError, UtilTargetError
	{
		if (Interpreter.DEBUG)
			Interpreter.debug("getIndex: " + array + ", index=" + index);
		try
		{
			Object val = Array.get(array, index);
			return Primitive.wrap(val, array.getClass().getComponentType());
		}
		catch (ArrayIndexOutOfBoundsException e1)
		{
			throw new UtilTargetError(e1);
		}
		catch (Exception e)
		{
			throw new ReflectError("Array access:" + e);
		}
	}

	public static void setIndex(Object array, int index, Object val) throws ReflectError,
			UtilTargetError
	{
		try
		{
			val = Primitive.unwrap(val);
			Array.set(array, index, val);
		}
		catch (ArrayStoreException e2)
		{
			throw new UtilTargetError(e2);
		}
		catch (IllegalArgumentException e1)
		{
			throw new UtilTargetError(new ArrayStoreException(e1.toString()));
		}
		catch (Exception e)
		{
			throw new ReflectError("Array access:" + e);
		}
	}

	public static Object getStaticFieldValue(Class clas, String fieldName) throws UtilEvalError,
			ReflectError
	{
		return getFieldValue(clas, null, fieldName, true/* onlystatic */);
	}

	/**
	*/
	public static Object getObjectFieldValue(Object object, String fieldName) throws UtilEvalError,
			ReflectError
	{
		if (object instanceof This)
			return ((This) object).namespace.getVariable(fieldName);
		else
		{
			try
			{
				return getFieldValue(object.getClass(), object, fieldName, false/* onlystatic */);
			}
			catch (ReflectError e)
			{
				// no field, try property acces

				if (hasObjectPropertyGetter(object.getClass(), fieldName))
					return getObjectProperty(object, fieldName);
				else
					throw e;
			}
		}
	}

	static LHS getLHSStaticField(Class clas, String fieldName) throws UtilEvalError, ReflectError
	{
		Field f = resolveExpectedJavaField(clas, fieldName, true/* onlystatic */);
		return new LHS(f);
	}

	/**
	 * Get an LHS reference to an object field.
	 * 
	 * This method also deals with the field style property access. In the field
	 * does not exist we check for a property setter.
	 */
	static LHS getLHSObjectField(Object object, String fieldName) throws UtilEvalError,
			ReflectError
	{
		if (object instanceof This)
		{
			// I guess this is when we pass it as an argument?
			// Setting locally
			boolean recurse = false;
			return new LHS(((This) object).namespace, fieldName, recurse);
		}

		try
		{
			Field f = resolveExpectedJavaField(object.getClass(), fieldName, false/* staticOnly */);
			return new LHS(object, f);
		}
		catch (ReflectError e)
		{
			// not a field, try property access
			if (hasObjectPropertySetter(object.getClass(), fieldName))
				return new LHS(object, fieldName);
			else
				throw e;
		}
	}

	private static Object getFieldValue(Class clas, Object object, String fieldName,
			boolean staticOnly) throws UtilEvalError, ReflectError
	{
		try
		{
			Field f = resolveExpectedJavaField(clas, fieldName, staticOnly);

			Object value = f.get(object);
			Class returnType = f.getType();
			return Primitive.wrap(value, returnType);

		}
		catch (NullPointerException e)
		{ // shouldn't happen
			throw new ReflectError("???" + fieldName + " is not a static field.");
		}
		catch (IllegalAccessException e)
		{
			throw new ReflectError("Can't access field: " + fieldName);
		}
	}

	/*
	 * Note: this method and resolveExpectedJavaField should be rewritten to
	 * invert this logic so that no exceptions need to be caught unecessarily.
	 * This is just a temporary impl.
	 * 
	 * @return the field or null if not found
	 */
	protected static Field resolveJavaField(Class clas, String fieldName, boolean staticOnly)
			throws UtilEvalError
	{
		try
		{
			return resolveExpectedJavaField(clas, fieldName, staticOnly);
		}
		catch (ReflectError e)
		{
			return null;
		}
	}

	/**
	 * @throws ReflectError
	 *             if the field is not found.
	 */
	/*
	 * Note: this should really just throw NoSuchFieldException... need to
	 * change related signatures and code.
	 */
	protected static Field resolveExpectedJavaField(Class clas, String fieldName, boolean staticOnly)
			throws UtilEvalError, ReflectError
	{
		Field field;
		try
		{
			if (Capabilities.haveAccessibility())
				field = findAccessibleField(clas, fieldName);
			else
				// Class getField() finds only public (and in interfaces, etc.)
				field = clas.getField(fieldName);
		}
		catch (NoSuchFieldException e)
		{
			throw new ReflectError("No such field: " + fieldName);
		}
		catch (SecurityException e)
		{
			throw new UtilTargetError("Security Exception while searching fields of: " + clas, e);
		}

		if (staticOnly && !Modifier.isStatic(field.getModifiers()))
			throw new UtilEvalError("Can't reach instance field: " + fieldName
					+ " from static context: " + clas.getName());

		return field;
	}

	/**
	 * Used when accessibility capability is available to locate an occurrance
	 * of the field in the most derived class or superclass and set its
	 * accessibility flag. Note that this method is not needed in the simple non
	 * accessible case because we don't have to hunt for fields. Note that
	 * classes may declare overlapping private fields, so the distinction about
	 * the most derived is important. Java doesn't normally allow this kind of
	 * access (super won't show private variables) so there is no real syntax
	 * for specifying which class scope to use...
	 * 
	 * @return the Field or throws NoSuchFieldException
	 * @throws NoSuchFieldException
	 *             if the field is not found
	 */
	/*
	 * This method should be rewritten to use getFields() and avoid catching
	 * exceptions during the search.
	 */
	private static Field findAccessibleField(Class clas, String fieldName) throws UtilEvalError,
			NoSuchFieldException
	{
		Field field;

		// Quick check catches public fields include those in interfaces
		try
		{
			field = clas.getField(fieldName);
			ReflectManager.RMSetAccessible(field);
			return field;
		}
		catch (NoSuchFieldException e)
		{
		}

		// Now, on with the hunt...
		while (clas != null)
		{
			try
			{
				field = clas.getDeclaredField(fieldName);
				ReflectManager.RMSetAccessible(field);
				return field;

				// Not found, fall through to next class

			}
			catch (NoSuchFieldException e)
			{
			}

			clas = clas.getSuperclass();
		}
		throw new NoSuchFieldException(fieldName);
	}

	/**
	 * This method wraps resolveJavaMethod() and expects a non-null method
	 * result. If the method is not found it throws a descriptive ReflectError.
	 */
	protected static Method resolveExpectedJavaMethod(BshClassManager bcm, Class clas,
			Object object, String name, Object[] args, boolean staticOnly) throws ReflectError,
			UtilEvalError
	{
		if (object == Primitive.NULL)
			throw new UtilTargetError(new NullPointerException("Attempt to invoke method " + name
					+ " on null value"));

		Class[] types = Types.getTypes(args);
		Method method = resolveJavaMethod(bcm, clas, name, types, staticOnly);

		if (method == null)
			throw new ReflectError((staticOnly ? "Static method " : "Method ")
					+ StringUtil.methodString(name, types) + " not found in class'"
					+ clas.getName() + "'");

		return method;
	}

	/**
	 * The full blown resolver method. All other method invocation methods
	 * delegate to this. The method may be static or dynamic unless staticOnly
	 * is set (in which case object may be null). If staticOnly is set then only
	 * static methods will be located.
	 * <p/>
	 * 
	 * This method performs caching (caches discovered methods through the class
	 * manager and utilizes cached methods.)
	 * <p/>
	 * 
	 * This method determines whether to attempt to use non-public methods based
	 * on Capabilities.haveAccessibility() and will set the accessibilty flag on
	 * the method as necessary.
	 * <p/>
	 * 
	 * If, when directed to find a static method, this method locates a more
	 * specific matching instance method it will throw a descriptive exception
	 * analogous to the error that the Java compiler would produce. Note: as of
	 * 2.0.x this is a problem because there is no way to work around this with
	 * a cast.
	 * <p/>
	 * 
	 * @param staticOnly
	 *            The method located must be static, the object param may be
	 *            null.
	 * @return the method or null if no matching method was found.
	 */
	protected static Method resolveJavaMethod(BshClassManager bcm, Class clas, String name,
			Class[] types, boolean staticOnly) throws UtilEvalError
	{
		if (clas == null)
			throw new InterpreterError("null class");

		// Lookup previously cached method
		Method method = null;
		if (bcm == null)
			Interpreter.debug("resolveJavaMethod UNOPTIMIZED lookup");
		else
			method = bcm.getResolvedMethod(clas, name, types, staticOnly);

		if (method == null)
		{
			boolean publicOnly = !Capabilities.haveAccessibility();
			// Searching for the method may, itself be a priviledged action
			try
			{
				method = findOverloadedMethod(clas, name, types, publicOnly);
			}
			catch (SecurityException e)
			{
				throw new UtilTargetError("Security Exception while searching methods of: " + clas,
						e);
			}

			checkFoundStaticMethod(method, staticOnly, clas);

			// This is the first time we've seen this method, set accessibility
			// Note: even if it's a public method, we may have found it in a
			// non-public class
			if (method != null && !publicOnly)
			{
				try
				{
					ReflectManager.RMSetAccessible(method);
				}
				catch (UtilEvalError e)
				{ /* ignore */
				}
			}

			// If succeeded cache the resolved method.
			if (method != null && bcm != null)
				bcm.cacheResolvedMethod(clas, types, method);
		}

		return method;
	}

	/**
	 * Get the candidate methods by searching the class and interface graph of
	 * baseClass and resolve the most specific.
	 * 
	 * @return the method or null for not found
	 */
	private static Method findOverloadedMethod(Class baseClass, String methodName, Class[] types,
			boolean publicOnly)
	{
		if (Interpreter.DEBUG)
			Interpreter.debug("Searching for method: " + StringUtil.methodString(methodName, types)
					+ " in '" + baseClass.getName() + "'");

		Method[] methods = getCandidateMethods(baseClass, methodName, types.length, publicOnly);

		if (Interpreter.DEBUG)
			Interpreter.debug("Looking for most specific method: " + methodName);
		Method method = findMostSpecificMethod(types, methods);

		return method;
	}

	/**
	 * Climb the class and interface inheritence graph of the type and collect
	 * all methods matching the specified name and criterion. If publicOnly is
	 * true then only public methods in *public* classes or interfaces will be
	 * returned. In the normal (non-accessible) case this addresses the problem
	 * that arises when a package private class or private inner class
	 * implements a public interface or derives from a public type.
	 * <p/>
	 * 
	 * This method primarily just delegates to gatherMethodsRecursive()
	 * 
	 * @see #gatherMethodsRecursive(Class, String, int, boolean,
	 *      java.util.Vector)
	 */
	static Method[] getCandidateMethods(Class baseClass, String methodName, int numArgs,
			boolean publicOnly)
	{
		Vector candidates = gatherMethodsRecursive(baseClass, methodName, numArgs, publicOnly, null/* candidates */);

		// return the methods in an array
		Method[] ma = new Method[candidates.size()];
		candidates.copyInto(ma);
		return ma;
	}

	/**
	 * Accumulate all methods, optionally including non-public methods, class
	 * and interface, in the inheritence tree of baseClass.
	 * 
	 * This method is analogous to Class getMethods() which returns all public
	 * methods in the inheritence tree.
	 * 
	 * In the normal (non-accessible) case this also addresses the problem that
	 * arises when a package private class or private inner class implements a
	 * public interface or derives from a public type. In other words, sometimes
	 * we'll find public methods that we can't use directly and we have to find
	 * the same public method in a parent class or interface.
	 * 
	 * @return the candidate methods vector
	 */
	private static Vector gatherMethodsRecursive(Class baseClass, String methodName, int numArgs,
			boolean publicOnly, Vector candidates)
	{
		if (candidates == null)
			candidates = new Vector();

		// Add methods of the current class to the vector.
		// In public case be careful to only add methods from a public class
		// and to use getMethods() instead of getDeclaredMethods()
		// (This addresses secure environments)
		if (publicOnly)
		{
			if (isPublic(baseClass))
				addCandidates(baseClass.getMethods(), methodName, numArgs, publicOnly, candidates);
		}
		else
			addCandidates(baseClass.getDeclaredMethods(), methodName, numArgs, publicOnly,
					candidates);

		// Does the class or interface implement interfaces?
		Class[] intfs = baseClass.getInterfaces();
		for (int i = 0; i < intfs.length; i++)
			gatherMethodsRecursive(intfs[i], methodName, numArgs, publicOnly, candidates);

		// Do we have a superclass? (interfaces don't, etc.)
		Class superclass = baseClass.getSuperclass();
		if (superclass != null)
			gatherMethodsRecursive(superclass, methodName, numArgs, publicOnly, candidates);

		return candidates;
	}

	private static Vector addCandidates(Method[] methods, String methodName, int numArgs,
			boolean publicOnly, Vector candidates)
	{
		for (int i = 0; i < methods.length; i++)
		{
			Method m = methods[i];
			if (m.getName().equals(methodName) && (m.getParameterTypes().length == numArgs)
					&& (!publicOnly || isPublic(m)))
				candidates.add(m);
		}
		return candidates;
	}

	/**
	 * Primary object constructor This method is simpler than those that must
	 * resolve general method invocation because constructors are not inherited.
	 * <p/>
	 * This method determines whether to attempt to use non-public constructors
	 * based on Capabilities.haveAccessibility() and will set the accessibilty
	 * flag on the method as necessary.
	 * <p/>
	 */
	static Object constructObject(Class clas, Object[] args) throws ReflectError,
			InvocationTargetException
	{
		if (clas.isInterface())
			throw new ReflectError("Can't create instance of an interface: " + clas);

		Object obj = null;
		Class[] types = Types.getTypes(args);
		Constructor con = null;

		// Find the constructor.
		// (there are no inherited constructors to worry about)
		Constructor[] constructors = Capabilities.haveAccessibility() ? clas
				.getDeclaredConstructors() : clas.getConstructors();

		if (Interpreter.DEBUG)
			Interpreter.debug("Looking for most specific constructor: " + clas);
		con = findMostSpecificConstructor(types, constructors);
		if (con == null)
			throw cantFindConstructor(clas, types);

		if (!isPublic(con))
			try
			{
				ReflectManager.RMSetAccessible(con);
			}
			catch (UtilEvalError e)
			{ /* ignore */
			}

		args = Primitive.unwrap(args);
		try
		{
			String proxyName = JavassistBootloader.getProxy(clas.getName());
			if (proxyName == null)
			{
				obj = con.newInstance(args);
			}
			else
			{
				Class<?> proxyClass = VM.currentVM().getLoader().loadClass(proxyName);
				Method pMethod = proxyClass.getMethod("_constructor", types);
				obj = invokeMethod(pMethod, null, args);
			}
		}
		catch (InstantiationException e)
		{
			throw new ReflectError("The class " + clas + " is abstract ");
		}
		catch (IllegalAccessException e)
		{
			throw new ReflectError("We don't have permission to create an instance."
					+ "Use setAccessibility(true) to enable access.");
		}
		catch (IllegalArgumentException e)
		{
			throw new ReflectError("The number of arguments was wrong");
		}
		catch (ClassNotFoundException e)
		{
			throw new ReflectError("Interal CloudSpace proxy class is missing.");
		}
		catch (SecurityException e)
		{
			throw new ReflectError("Security Exception on a CloudSpace Proxy Class");
		}
		catch (NoSuchMethodException e)
		{
			throw new ReflectError(
					"A proxy method was used by CloudSpace; however, the proxy class does not proxy the method in question");
		}
		if (obj == null)
			throw new ReflectError("Couldn't construct the object");

		return obj;
	}

	/*
	 * This method should parallel findMostSpecificMethod() The only reason it
	 * can't be combined is that Method and Constructor don't have a common
	 * interface for their signatures
	 */
	static Constructor findMostSpecificConstructor(Class[] idealMatch, Constructor[] constructors)
	{
		int match = findMostSpecificConstructorIndex(idealMatch, constructors);
		return (match == -1) ? null : constructors[match];
	}

	static int findMostSpecificConstructorIndex(Class[] idealMatch, Constructor[] constructors)
	{
		Class[][] candidates = new Class[constructors.length][];
		for (int i = 0; i < candidates.length; i++)
			candidates[i] = constructors[i].getParameterTypes();

		return findMostSpecificSignature(idealMatch, candidates);
	}

	/**
	 * Find the best match for signature idealMatch. It is assumed that the
	 * methods array holds only valid candidates (e.g. method name and number of
	 * args already matched). This method currently does not take into account
	 * Java 5 covariant return types... which I think will require that we find
	 * the most derived return type of otherwise identical best matches.
	 * 
	 * @see #findMostSpecificSignature(Class[], Class[][])
	 * @param methods
	 *            the set of candidate method which differ only in the types of
	 *            their arguments.
	 */
	static Method findMostSpecificMethod(Class[] idealMatch, Method[] methods)
	{
		// copy signatures into array for findMostSpecificMethod()
		Class[][] candidateSigs = new Class[methods.length][];
		for (int i = 0; i < methods.length; i++)
			candidateSigs[i] = methods[i].getParameterTypes();

		int match = findMostSpecificSignature(idealMatch, candidateSigs);
		return match == -1 ? null : methods[match];
	}

	/**
	 * Implement JLS 15.11.2 Return the index of the most specific arguments
	 * match or -1 if no match is found. This method is used by both methods and
	 * constructors (which unfortunately don't share a common interface for
	 * signature info).
	 * 
	 * @return the index of the most specific candidate
	 */
	/*
	 * Note: Two methods which are equally specific should not be allowed by the
	 * Java compiler. In this case BeanShell currently chooses the first one it
	 * finds. We could add a test for this case here (I believe) by adding
	 * another isSignatureAssignable() in the other direction between the target
	 * and "best" match. If the assignment works both ways then neither is more
	 * specific and they are ambiguous. I'll leave this test out for now because
	 * I'm not sure how much another test would impact performance. Method
	 * selection is now cached at a high level, so a few friendly extraneous
	 * tests shouldn't be a problem.
	 */
	static int findMostSpecificSignature(Class[] idealMatch, Class[][] candidates)
	{
		for (int round = Types.FIRST_ROUND_ASSIGNABLE; round <= Types.LAST_ROUND_ASSIGNABLE; round++)
		{
			Class[] bestMatch = null;
			int bestMatchIndex = -1;

			for (int i = 0; i < candidates.length; i++)
			{
				Class[] targetMatch = candidates[i];

				// If idealMatch fits targetMatch and this is the first match
				// or targetMatch is more specific than the best match, make it
				// the new best match.
				if (Types.isSignatureAssignable(idealMatch, targetMatch, round)
						&& ((bestMatch == null) || Types.isSignatureAssignable(targetMatch,
								bestMatch, Types.JAVA_BASE_ASSIGNABLE)))
				{
					bestMatch = targetMatch;
					bestMatchIndex = i;
				}
			}

			if (bestMatch != null)
				return bestMatchIndex;
		}

		return -1;
	}

	private static String accessorName(String getorset, String propName)
	{
		return getorset + String.valueOf(Character.toUpperCase(propName.charAt(0)))
				+ propName.substring(1);
	}

	public static boolean hasObjectPropertyGetter(Class clas, String propName)
	{
		String getterName = accessorName("get", propName);
		try
		{
			clas.getMethod(getterName, new Class[0]);
			return true;
		}
		catch (NoSuchMethodException e)
		{ /* fall through */
		}
		getterName = accessorName("is", propName);
		try
		{
			Method m = clas.getMethod(getterName, new Class[0]);
			return (m.getReturnType() == Boolean.TYPE);
		}
		catch (NoSuchMethodException e)
		{
			return false;
		}
	}

	public static boolean hasObjectPropertySetter(Class clas, String propName)
	{
		String setterName = accessorName("set", propName);
		Method[] methods = clas.getMethods();

		// we don't know the right hand side of the assignment yet.
		// has at least one setter of the right name?
		for (int i = 0; i < methods.length; i++)
			if (methods[i].getName().equals(setterName))
				return true;
		return false;
	}

	public static Object getObjectProperty(Object obj, String propName) throws UtilEvalError,
			ReflectError
	{
		Object[] args = new Object[] {};

		Interpreter.debug("property access: ");
		Method method = null;

		Exception e1 = null, e2 = null;
		try
		{
			String accessorName = accessorName("get", propName);
			method = resolveExpectedJavaMethod(null/* bcm */, obj.getClass(), obj, accessorName,
					args, false);
		}
		catch (Exception e)
		{
			e1 = e;
		}
		if (method == null)
			try
			{
				String accessorName = accessorName("is", propName);
				method = resolveExpectedJavaMethod(null/* bcm */, obj.getClass(), obj,
						accessorName, args, false);
				if (method.getReturnType() != Boolean.TYPE)
					method = null;
			}
			catch (Exception e)
			{
				e2 = e;
			}
		if (method == null)
			throw new ReflectError("Error in property getter: " + e1
					+ (e2 != null ? " : " + e2 : ""));

		try
		{
			return invokeMethod(method, obj, args);
		}
		catch (InvocationTargetException e)
		{
			throw new UtilEvalError("Property accessor threw exception: " + e.getTargetException());
		}
	}

	public static void setObjectProperty(Object obj, String propName, Object value)
			throws ReflectError, UtilEvalError
	{
		String accessorName = accessorName("set", propName);
		Object[] args = new Object[] { value };

		Interpreter.debug("property access: ");
		try
		{
			Method method = resolveExpectedJavaMethod(null/* bcm */, obj.getClass(), obj,
					accessorName, args, false);
			invokeMethod(method, obj, args);
		}
		catch (InvocationTargetException e)
		{
			throw new UtilEvalError("Property accessor threw exception: " + e.getTargetException());
		}
	}

	/**
	 * Return a more human readable version of the type name. Specifically,
	 * array types are returned with postfix "[]" dimensions. e.g. return
	 * "int []" for integer array instead of "class [I" as would be returned by
	 * Class getName() in that case.
	 */
	public static String normalizeClassName(Class type)
	{
		if (!type.isArray())
			return type.getName();

		StringBuffer className = new StringBuffer();
		try
		{
			className.append(getArrayBaseType(type).getName() + " ");
			for (int i = 0; i < getArrayDimensions(type); i++)
				className.append("[]");
		}
		catch (ReflectError e)
		{ /* shouldn't happen */
		}

		return className.toString();
	}

	/**
	 * returns the dimensionality of the Class returns 0 if the Class is not an
	 * array class
	 */
	public static int getArrayDimensions(Class arrayClass)
	{
		if (!arrayClass.isArray())
			return 0;

		return arrayClass.getName().lastIndexOf('[') + 1; // why so cute?
	}

	/**
	 * Returns the base type of an array Class. throws ReflectError if the Class
	 * is not an array class.
	 */
	public static Class getArrayBaseType(Class arrayClass) throws ReflectError
	{
		if (!arrayClass.isArray())
			throw new ReflectError("The class is not an array.");

		return arrayClass.getComponentType();

	}

	/**
	 * A command may be implemented as a compiled Java class containing one or
	 * more static invoke() methods of the correct signature. The invoke()
	 * methods must accept two additional leading arguments of the interpreter
	 * and callstack, respectively. e.g. invoke(interpreter, callstack, ... )
	 * This method adds the arguments and invokes the static method, returning
	 * the result.
	 */
	public static Object invokeCompiledCommand(Class commandClass, Object[] args,
			Interpreter interpreter, CallStack callstack) throws UtilEvalError
	{
		// add interpereter and namespace to args list
		Object[] invokeArgs = new Object[args.length + 2];
		invokeArgs[0] = interpreter;
		invokeArgs[1] = callstack;
		System.arraycopy(args, 0, invokeArgs, 2, args.length);
		BshClassManager bcm = interpreter.getClassManager();
		try
		{
			return Reflect.invokeStaticMethod(bcm, commandClass, "invoke", invokeArgs);
		}
		catch (InvocationTargetException e)
		{
			throw new UtilEvalError("Error in compiled command: " + e.getTargetException());
		}
		catch (ReflectError e)
		{
			throw new UtilEvalError("Error invoking compiled command: " + e);
		}
	}

	private static void logInvokeMethod(String msg, Method method, Object[] args)
	{
		if (Interpreter.DEBUG)
		{
			Interpreter.debug(msg + method + " with args:");
			for (int i = 0; i < args.length; i++)
				Interpreter.debug("args[" + i + "] = " + args[i] + " type = " + args[i].getClass());
		}
	}

	private static void checkFoundStaticMethod(Method method, boolean staticOnly, Class clas)
			throws UtilEvalError
	{
		// We're looking for a static method but found an instance method
		if (method != null && staticOnly && !isStatic(method))
			throw new UtilEvalError("Cannot reach instance method: "
					+ StringUtil.methodString(method.getName(), method.getParameterTypes())
					+ " from static context: " + clas.getName());
	}

	private static ReflectError cantFindConstructor(Class clas, Class[] types)
	{
		if (types.length == 0)
			return new ReflectError("Can't find default constructor for: " + clas);
		else
			return new ReflectError("Can't find constructor: "
					+ StringUtil.methodString(clas.getName(), types) + " in class: "
					+ clas.getName());
	}

	private static boolean isPublic(Class c)
	{
		return Modifier.isPublic(c.getModifiers());
	}

	private static boolean isPublic(Method m)
	{
		return Modifier.isPublic(m.getModifiers());
	}

	private static boolean isPublic(Constructor c)
	{
		return Modifier.isPublic(c.getModifiers());
	}

	private static boolean isStatic(Method m)
	{
		return Modifier.isStatic(m.getModifiers());
	}
}
